#include "Shader.h"

#include "MVPShader.h"
#include "ShadowMapShader.h"
#include "SkyBoxShader.h"
#include "ColorShader.h"
#include "GUIShader.h"

#include "Texture.h"

#include "CheckError.h"

#include <fstream>
#include <iostream>
#include <vector>

std::map<std::string, std::shared_ptr<Shader>> Shader::shaders;
std::vector<std::string> Shader::paths;

Shader::Shader() 
{
}

Shader::~Shader()
{
	glDetachShader(programID, vertexShaderID);
	glDetachShader(programID, fragmentShaderID);

	glDeleteProgram(programID);
	
	glDeleteShader(vertexShaderID);
	glDeleteShader(fragmentShaderID);
}

void Shader::CompileShader()
{
	Init();
}

void Shader::Init()
{
	GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

	//open stream for vertex file
	std::string vertString = LoadShaderFromFile(vertFilePath);

	//open stream for fragment file
	std::string fragString = LoadShaderFromFile(fragFilePath);

	GLint result = GL_FALSE;
	int infoLogLength;

	//compile vertex shader
	std::cout << "Compiling shader: " << vertFilePath << std::endl;
	const char *vertStringPtr = vertString.c_str();
	glShaderSource(vertexShaderID, 1, &vertStringPtr, NULL);
	glCompileShader(vertexShaderID);

	// Check Vertex Shader
    glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &result);
    glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
   // std::vector<char> VertexShaderErrorMessage(infoLogLength);
   // glGetShaderInfoLog(vertexShaderID, infoLogLength, NULL, &VertexShaderErrorMessage[0]);
	//std::cout << &VertexShaderErrorMessage[0] << std::endl;

	//compile fragment shader
	std::cout << "Compiling shader: " << fragFilePath << std::endl;
	const char *fragStringPtr = fragString.c_str();
	glShaderSource(fragmentShaderID, 1, &fragStringPtr, NULL);
	glCompileShader(fragmentShaderID);
	
	// Check Fragment Shader
    glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &result);
    glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
   // std::vector<char> FragShaderErrorMessage(infoLogLength);
   // glGetShaderInfoLog(fragmentShaderID, infoLogLength, NULL, &FragShaderErrorMessage[0]);
//	std::cout << &FragShaderErrorMessage[0] << std::endl;

	//linking program
	std::cout << "Linking program" << std::endl;
	programID = glCreateProgram();
	glAttachShader(programID, vertexShaderID);
	glAttachShader(programID, fragmentShaderID);
    glLinkProgram(programID);

	glGetProgramiv(programID, GL_LINK_STATUS, &result);
    glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength);
    std::vector<char> ProgramErrorMessage( std::max(infoLogLength, int(1)) );
    glGetProgramInfoLog(programID, infoLogLength, NULL, &ProgramErrorMessage[0]);
    std::cout << &ProgramErrorMessage[0] << std::endl;
 
	this->vertexShaderID = vertexShaderID;
	this->fragmentShaderID = fragmentShaderID;
}

std::string Shader::LoadShaderFromFile(std::string filePath)
{
	std::vector<std::string> paths = GetPaths();

	for(int i=0; i<paths.size(); i++)
	{
		std::string resultString;
		std::ifstream shaderStream(paths.at(i) + filePath, std::ios::in);
		if(shaderStream.is_open()) 
		{
			std::string line = "";
			while(std::getline(shaderStream, line))
			{
				resultString += "\n" + line;
			}
			shaderStream.close();
			return resultString;
		}
		else 
		{
			std::cerr << "Shader: "<< filePath << " not found" << std::endl;
		}
	}
	
	return "";
}


//return program, vertex, and frag ID

GLuint Shader::getProgramID()
{
	return programID;
}

void Shader::InitShaders()
{
	paths.push_back(std::string("D:\\Projects\\WRenderer\\WRenderer\\Data\\Shaders\\"));
	shaders.insert(std::make_pair<std::string, std::shared_ptr<Shader>>(std::string("MVPShader"), std::shared_ptr<Shader>(new MVPShader())));
	shaders.insert(std::make_pair<std::string, std::shared_ptr<Shader>>(std::string("ShadowMapShader"), std::shared_ptr<Shader>(new ShadowMapShader())));
	shaders.insert(std::make_pair<std::string, std::shared_ptr<Shader>>(std::string("SkyBoxShader"), std::shared_ptr<Shader>(new SkyBoxShader())));
	shaders.insert(std::make_pair<std::string, std::shared_ptr<Shader>>(std::string("ColorShader"), std::shared_ptr<Shader>(new ColorShader())));
	shaders.insert(std::make_pair<std::string, std::shared_ptr<Shader>>(std::string("GUIShader"), std::shared_ptr<Shader>(new GUIShader())));
}

void Shader::LoadShaders()
{
	std::map<std::string, std::shared_ptr<Shader>>::iterator it;
	for(it = shaders.begin(); it != shaders.end(); it++)
	{
		(*it).second->CompileShader();
	}
}

void Shader::Bind()
{
	glUseProgram(this->programID);
	int i = 0;
	for(TextureMap::iterator it = texParameters.begin(); it != texParameters.end(); it++)
	{
		GLuint textureID = (*it).second;

		glActiveTexture(GL_TEXTURE0 + i);
		//tex->Bind();
		glBindTexture(GL_TEXTURE_2D, textureID);

		i++;
	}
}
	
void Shader::Unbind()
{
	int i = 0;
	for(Shader::TextureMap::iterator it = texParameters.begin(); it != texParameters.end(); it++)
	{
		glActiveTexture(GL_TEXTURE0 + i);
		//tex->Unbind();
		glBindTexture(GL_TEXTURE_2D, 0);

		i++;
	}

	glUseProgram(0);
}

std::vector<std::string> Shader::GetPaths()
{
	return paths;
}

std::shared_ptr<Shader> Shader::GetShader(std::string shaderName)
{
	return shaders.at(shaderName);
}


std::shared_ptr<FloatShaderUniform> Shader::AddParameter(std::string name, float parameter)
{
	std::shared_ptr<FloatShaderUniform> uParam = std::shared_ptr<FloatShaderUniform>(new FloatShaderUniform(name, parameter));
	parameters[name] = uParam;

	return uParam;
}

std::shared_ptr<Vec2ShaderUniform> Shader::AddParameter(std::string name, glm::vec2 parameter)
{
	std::shared_ptr<Vec2ShaderUniform> uParam = std::shared_ptr<Vec2ShaderUniform>(new Vec2ShaderUniform(name, parameter));
	parameters[name] = uParam;

	return uParam;
}

std::shared_ptr<Vec3ShaderUniform> Shader::AddParameter(std::string name, glm::vec3 parameter)
{
	std::shared_ptr<Vec3ShaderUniform> uParam = std::shared_ptr<Vec3ShaderUniform>(new Vec3ShaderUniform(name, parameter));
	parameters[name] = uParam;

	return uParam;
}

std::shared_ptr<UniformBlockUniform> Shader::AddParameter(std::string name, void* uniformBlock, GLuint size, GLuint bindingPt)
{
	std::shared_ptr<UniformBlockUniform> uParam = std::shared_ptr<UniformBlockUniform>(new UniformBlockUniform(name, uniformBlock, size, this->getProgramID(), bindingPt));
	parameters[name] = uParam;
	return uParam;
}

void Shader::AddParameter(TextureType type, std::shared_ptr<Texture> texParameter)
{
	texParameters.insert(std::pair<TextureType, GLuint>(type, texParameter->GetTextureID()));
}


void Shader::AddParameter(TextureType type, GLuint texID)
{
	texParameters.insert(std::pair<TextureType, GLuint>(type, texID));
}
